#include <math.h>
#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include "common.h"
#include "graph.h"
#include "prim.h"
#include "euler.h"
#include "array.h"
#include "hamilton.h"
#include "triangulacja.h"


void remainingWork(Dane verts, int* indices, int numTris, int rank, int np);
Dane readInput(char* filePath);


int main(int argc, char *argv[]) {
	int rank, size;
	MPI_Status status;
	int i,j,l;
	double start, finish;
	int* buf;
	int* tmpBuf;
	int tmpL;
	Dane dane;
	FILE* fp;

	// Inicjalizacja
	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);

	// Inicjalizacja zegara
	start = MPI_Wtime();
	clock();
	
	if (rank == 0) {// proces master
		/* Wczytanie danych */
		if(argc != 2){
			printf("Bledna ilosc parametrow.\n");
			MPI_Finalize();
			return 1;
		}
		dane = readInput(argv[1]);

		
		MPI_Bcast(&dane.n, 1, MPI_INT, 0, MPI_COMM_WORLD);
        buf = new(int, 6 * dane.n);

		l = triangulation(dane, rank, size, buf);


		tmpBuf = new(int, 6 * dane.n);
		for (i = 1; i < size; i++) {
			MPI_Recv(&tmpL, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			MPI_Recv(tmpBuf, tmpL, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			for (j = 0; j < tmpL; j+=3){
				buf[l + j + 0] = tmpBuf[j + 0];
				buf[l + j + 1] = tmpBuf[j + 1];
				buf[l + j + 2] = tmpBuf[j + 2];
			}
			l += tmpL;
		}

		// WYNIK
		fp = fopen("triangulacja.out", "w");
		if (!fp) {
			printf("Nie mozna otworzyc pliku wynikowego.\n");
			return 1;
		}
		fprintf(fp, "%d\n", l);	
		for (i = 0; i < l; i+=3) {
			fprintf(fp, "%d %d %d\n", buf[i + 0], buf[i + 1], buf[i + 2]);
		}
		fclose(fp);
		/*
		// Odebranie wynikow
		for (i = 1; i < size; i++) {
			MPI_Recv(&l, 1, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			MPI_Recv(buf, l, MPI_INT, i, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			for (j = 0; j < l; j+=3)
				fprintf(fp, "%d %d %d\n", buf[j + 0], buf[j + 1], buf[j + 2]);
		}
		fclose(fp);*/
	}
	// pozostale procesy
	else {
		MPI_Bcast(&dane.n, 1, MPI_INT, 0, MPI_COMM_WORLD);
        buf = new(int, 6 * dane.n);

		l = triangulation(dane, rank, size, buf);

		//wyslanie wynikow
		MPI_Send(&l, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
		MPI_Send(buf, l, MPI_INT, 0, 0, MPI_COMM_WORLD);
	}
	MPI_Barrier(MPI_COMM_WORLD);

	finish = MPI_Wtime();
	if (rank == 0) {
		printf("Elapsed time = %.2f s\n", finish - start);
		printf("Master CPU time = %.2f s\n", cast(double) clock()/CLOCKS_PER_SEC);
	}
	else {
		printf("Slave %d CPU time = %.2f s\n", rank, cast(double) clock()/CLOCKS_PER_SEC);
	}

	remainingWork(dane, buf, l/3, rank, size);


	// Koniec
	MPI_Finalize();
	return 0;
}





void remainingWork(Dane verts, int* indices, int numTris, int rank, int np) {
	Graph graph;
	uint64 processingTime;
	intArray eulerCycle, hamiltonCycle;
	int i;
	Vertex v1, v2;
	FILE *fp;
	double finalLen = 0;


	if (0 == rank) {
		graph = trisToGraph(verts, indices, numTris);
		printGraphConns(graph);
	} else {
		graph.numNodes = 0;
	}

	broadcastGraph(&graph, rank, np);

   if (0 == rank) {
      reduceGraph(graph, 0, graph.numNodes / np);
   }

   /* Wait till the graph reaches every process so we can time more precisely */
   MPI_Barrier(MPI_COMM_WORLD);

   processingTime = processDijkstra(graph, 0, rank, np);

   if (0 == rank) {
      printGraph(graph);
   }

   printf("Node %d Computation time: %lld us\n", rank, processingTime);

   if (0 == rank) {
      graphToTreeFromPrevList(graph);
      printGraphConns(graph);
		printGraphConnsModToFile(graph, "emts.out");

      findEulerCycle(graph, &eulerCycle);

      printf("Euler cycle: ");
	  fp = fopen("euler.out", "w");
	  if (fp) {
	    fprintf(fp, "%d\n", eulerCycle.length);
	    for (i = 0; i < eulerCycle.length; ++i)
            fprintf(fp, "%d ", eulerCycle.data[i]);
		fclose(fp);
	  } 
      for (i = 0; i < eulerCycle.length; ++i) {
         printf(" %d", eulerCycle.data[i]);
      }
      printf("\n\n");


		hamiltonCycle = eulerToHamilton(eulerCycle, graph.numNodes);
      printf("Hamilton cycle: ");
	  fp = fopen("hamilton.out", "w");
	  if (fp) {
	    fprintf(fp, "%d\n",  hamiltonCycle.length);
	    for (i = 0; i <  hamiltonCycle.length; ++i)
            fprintf(fp, "%d ",  hamiltonCycle.data[i]);
		fclose(fp);
	  } 
      for (i = 0; i < hamiltonCycle.length; ++i) {
         printf(" %d", hamiltonCycle.data[i]);

         v1 = verts.vert[hamiltonCycle.data[i]];
         v2 = verts.vert[hamiltonCycle.data[(i+1) % hamiltonCycle.length]];

		 finalLen += sqrt((v1.x-v2.x)*(v1.x-v2.x) + (v1.y-v2.y)*(v1.y-v2.y));
      }
      printf("\n");
      printf("Circuit length: %lf\n", finalLen);
   }
}


Dane readInput(char* filePath){
	int i;
	Dane res;
	FILE* fp;

	fp = fopen(filePath, "r");
	if (!fp) {
		printf("Otwarcie pliku nie powiodlo sie.\n");
		MPI_Finalize();
		exit(1);
	}
	fscanf(fp, "%d", &res.n);
	res.vert = new(Vertex, res.n);

	for (i = 0; i < res.n; i++)
		fscanf(fp, "%lf %lf", &res.vert[i].x, &res.vert[i].y);
	fclose(fp);
	return res;
}

